# power by luo_tao 20211111
import os
import time
from ftplib import FTP
import traceback


# 如需要支持中文名文件传输，需要将ftplib.py文件中的  encoding = "latin-1"  改为   encoding = "utf-8"
class FTP1(FTP):  # 继承FTP类，修改解码参数，解决不识别文件名中文的问题
    encoding = "utf-8"


def is_upload(ftp, local_file, remote_file):
    result = is_modify(ftp, local_file, remote_file)
    if result:
        print(f"文件{local_file}需要上传【上传原因：发生修改】")
        # 直接返回要上传
        return True
    result = is_same_size(ftp, local_file, remote_file)
    if result:
        print(f"文件{local_file}需要上传【上传原因：文件不存在或大小发生变化】")
        return True
    else:
        print(f"文件{local_file}不需要上传")
        return False


# 判断本地文件最后修改时间是否大于ftp服务器文件最后修改时间
def is_modify(ftp, local_file, remote_file):
    # t = os.path.getmtime(local_file)
    local_modify_time_stamp = int(os.path.getmtime(local_file))
    # mtime = time.stripe(os.path.getmtime(local_file))
    # print(mtime)
    # print("本地文件最后修改时间：" + t)
    try:
        timestamp = ftp.voidcmd("MDTM " + remote_file)[4:].strip()
        timeArray = time.strptime(timestamp, "%Y%m%d%H%M%S")
        # 转换为时间戳:
        remote_modify_time_stamp = int(time.mktime(timeArray)) + 28800
        # print("本地文件修改时间:" + str(local_modify_time_stamp))
        # print("远程文件修改时间:" + str(remote_modify_time_stamp))
        # 如果本地文件修改时间大于远程文件时间，则返回true进行修改
    except Exception as err:
        return True
    if local_modify_time_stamp > remote_modify_time_stamp:
        return True
    else:
        return False


# 比对本地文件和上传文件的大小
def is_same_size(ftp, local_file, remote_file):
    try:
        remote_file_size = ftp.size(remote_file)  # 获取远端文件大小
    except Exception as err:
        # app_log.debug("获取远程文件大小失败, 原因为:%s" % err)
        remote_file_size = -1  # 如果获取FTP文件失败，则返回-1
    try:
        local_file_size = os.path.getsize(local_file)  # 获取本地文件大小

    except Exception as err:
        local_file_size = -1  # 如果获取本地文件失败，则返回-1
    # 三目运算符
    return False if (remote_file_size == local_file_size) else True


# 上传单个文件的函数,里面调用了比对本地文件和上传文件的大小的函数
def upload_file(ftp, local_file, remote_file):
    global upload_file_count
    global fail_count
    # 检查本地是否有此文件
    if not os.path.exists(local_file):  # 如果不存在本地文件，记录日志并返回False
        print("不存在本地文件")
    result = is_upload(ftp, local_file, remote_file)  # FTP如果文件已存在，则对比大小
    # 需要上传文件
    if result:
        print(f'上传文件：远程文件 {remote_file} 不存在，现在开始上传...')
        global FTP_PERFECT_BUFF_SIZE  # 把全局变量传进来
        try:  # 上传文件到FTP
            with open(local_file, 'rb') as f:  # 打开本地文件
                if ftp.storbinary('STOR ' + remote_file, f):  # 上传本地文件到FTP
                    result, remote_file_size, local_file_size = is_same_size(ftp, local_file, remote_file)
                print(f'{remote_file}文件上传成功, 远程文件大小 = {remote_file_size}, 本地文件大小 = {local_file_size}')
                upload_file_count += 1
        except Exception as err:
            print(f'上传文件有错误发生:{local_file}, 错误:{err}')
            fail_count += 1
            result = False


# 上传目录的函数，里面有调用上传单个文件的函数
def upload_file_tree(local_path, remote_path, ftp, IsRecursively):
    global fail_count
    # 有远端目录的话进入目录，没有目录的话创建目录
    # print(f'upload_file_tree函数开始运行！FTP远程目录为:{remote_path}')
    # 切换到FTP的目标目录，如果没有的话则创建
    try:
        ftp.cwd(remote_path)  # 进入FTP工作目录
    except Exception as e:
        print(f'FTP目录：{remote_path}文件夹不存在，错误信息:', e)
        # base_dir, part_path = ftp.pwd(), remote_path.split('/')
        base_dir, part_path = '', remote_path.split('/')
        for subpath in part_path:
            # 针对类似  '/home/billing/scripts/zhf/send' 和 'home/billing/scripts/zhf/send' 两种格式的目录
            # 如果第一个分解后的元素是''这种空字符，说明根目录是从/开始，如果最后一个是''这种空字符，说明目录是以/结束
            # 例如 /home/billing/scripts/zhf/send/ 分解后得到 ['', 'home', 'billing', 'scripts', 'zhf', 'send', ''] 首位和尾都不是有效名称
            if '' == subpath:  # 如果是空字符，跳出此循环，执行下一个。
                continue
            base_dir = base_dir + '/' + subpath  # base_dir + subpath  # 拼接子目录
            try:
                ftp.cwd(base_dir)  # 进入目录
            except Exception as e:
                print(f'创建FTP目录:{base_dir}')
                ftp.mkd(base_dir)  # 不存在创建当前子目录 直到创建所有
                continue
        ftp.cwd(remote_path)  # 进入FTP工作目录
    # 本地目录切换
    try:
        # 远端目录通过ftp对象已经切换到指定目录或创建的指定目录
        file_list = os.listdir(local_path)  # 列出本地文件夹第一层目录的所有文件和目录
        for file_name in file_list:
            if os.path.isdir(os.path.join(local_path, file_name)):  # 判断是文件还是目录，是目录为真
                if IsRecursively:  # 递归变量，默认为Ture
                    # 使用FTP进入远程目录，如果没有远程目录则创建它
                    try:  # 如果是目录，则尝试进入到这个目录，再退出来。
                        cwd = ftp.pwd()  # 获取FTP当前路径
                        ftp.cwd(file_name)  # 如果cwd成功 则表示该目录存在 退出到上一级
                        ftp.cwd(cwd)  # 再返回FTP之前的目录
                    except Exception as e:
                        print(f'检查FTP远程目录{file_name}不存在, 现在创建，错误信息:{e}')
                        ftp.mkd(file_name)  # 建立目录
                        print(f'在{remote_path}目录中新建子目录 {file_name} ...')
                    p_local_path = os.path.join(local_path, file_name)  # 拼接本地第一层子目录，递归时进入下一层
                    # 远程是linux
                    p_remote_path = ftp.pwd() + '/' + file_name  # 拼接FTP第一层子目录，递归时进入下一层
                    # p_remote_path = os.path.join(ftp.pwd(), file_name)  # 拼接FTP第一层子目录，递归时进入下一层
                    upload_file_tree(p_local_path, p_remote_path, ftp, IsRecursively)  # 递归
                    ftp.cwd("..")  # 对于递归 ftp 每次传输完成后需要切换目录到上一级
                else:
                    continue
            else:
                # 是文件 直接上传
                local_file = os.path.join(local_path, file_name)
                # 这里远端要用linux的
                remote_file = remote_path + '/' + file_name
                # remote_file = os.path.join(remote_path, file_name)
                upload_file(ftp, local_file, remote_file)
    except:
        return
    return


# 计算本地文件夹中文件个数
def file_count(local_path, type_dict):
    global local_file_count  # 声明全局变量
    file_list = os.listdir(local_path)  # 列出本地文件夹第一层目录的所有文件和目录
    for file_name in file_list:
        if os.path.isdir(os.path.join(local_path, file_name)):  # 判断是文件还是目录，是目录为真
            type_dict.setdefault("文件夹", 0)  # 如果字典key不存在，则添加并设置为初始值
            type_dict["文件夹"] += 1
            p_local_path = os.path.join(local_path, file_name)  # 拼接本地第一层子目录，递归时进入下一层
            file_count(p_local_path, type_dict)
        else:
            ext = os.path.splitext(file_name)[1]  # 获取到文件的后缀
            type_dict.setdefault(ext, 0)  # 如果字典key不存在，则添加并设置为初始值
            type_dict[ext] += 1
            local_file_count += 1  # 计算总文件数量
    return local_file_count


local_file_count = 0  # 计算本地总文件数,初始为0
upload_file_count = 0  # 计算有多少文件上传到了FTP
fail_count = 0  # 失败计数


def scan_upload_files(ftp, local_path, remote_path):
    global local_file_count  # 声明全局变量
    global upload_file_count
    global fail_count
    # 定义变量
    IsRecursively = True  # 是否复制子目录下文件的开关，不需要可配置为False
    type_dict = dict()  # 定义一个保存文件类型及数量的空字典

    # 本地文件计数和上传文件
    file_count(local_path, type_dict)  # 运行计算本地文件夹文件数量的函数
    upload_file_tree(local_path, remote_path, ftp, IsRecursively)

    # 打印文件上传结果
    for each_type in type_dict:
        if each_type == '文件夹':
            continue
        print(f"目录[{local_path}]中文件类型为[{each_type}]的数量有：{type_dict[each_type]} 个")
    print(f"目录[{local_path}]本地文件数量为:{local_file_count}，本次FTP文件上传数量为:{upload_file_count}")
    # if fail_count:
    #     print("本地文件上传FTP有失败记录，请检查日志！")
    local_file_count = 0
    upload_file_count = 0
    fail_count = 0


if __name__ == '__main__':
    # 配置连接FTP的参数
    ftp = FTP()
    # 服务器IP和端口
    ftp.connect("relosy.cn", 21)
    ftp.encoding = "utf-8"
    # 匿名登陆, 如果需要登陆, 就把两个空字符串写上用户名和密码就行了("username", "password")
    ftp.login(user="root", passwd="Avenge1026")
    ftp.set_pasv(True)
    scan_upload_files(ftp, 'D:\\cloudfile', '/myftp/1')
